import mx.events.EventDispatcher;
import mx.utils.Delegate;
import org.molamil.acto.core.EventConstants;
import org.molamil.acto.core.Logger;
import org.molamil.acto.pages.PageBuilder;
import org.molamil.acto.pages.PageContext;
import org.molamil.acto.pages.PagesConf;
import org.molamil.acto.pages.preload.PageLoad;
import org.molamil.acto.pages.preload.PagePinger;
import org.molamil.acto.pages.preload.PagePreloader;
import org.molamil.acto.pages.preload.PreloaderNotStoppedError; 

/**
 * XXX: Comment.
 * 
 * @author Jorge Hernandez
 */
class org.molamil.acto.pages.preload.BasicPagePreloader implements PagePreloader, PagePinger {
	
	
	//FIXME: It takes long from the point when it stops loading and starts the next load.
	
	
	// Properties
	
	private static var log:Logger = Logger.getLogger("BasicPagePreloader");
	
	private var pingingPages:Array;
	private var preloadingPages:Array;
	private var pingContainer_mc:MovieClip;	private var preloadContainer_mc:MovieClip;
	private var isPreloadStop:Boolean;
	
	private var _pages:Array;
	private var _pingedPages:Array;
	private var _preloadedPages:Array;
	private var _isPinging:Boolean;
	private var _isPreloading:Boolean;
	private var _isPinged:Boolean;
	private var _isPreloaded:Boolean;
	
	private var pingStartDelegate:Function;
	private var pingProgressDelegate:Function;
	private var pingErrorDelegate:Function;
	private var preloadStartDelegate:Function;
	private var preloadProgressDelegate:Function;
	private var preloadInitDelegate:Function;
	private var preloadErrorDelegate:Function;
	
	
	// Constructor
	
	public function BasicPagePreloader(pages:Array) {
		
		log.debug("Constructor, pages.length = " + pages.length);
		
		EventDispatcher.initialize(this);
		
		if (pages != null)
			this.pages = pages;
		
		preloadingPages = new Array();	
		_preloadedPages = new Array();
		_isPinging = false;
		_isPreloading = false;
		_isPinged = false;		_isPreloaded = false;
		isPreloadStop = false;
		
		pingStartDelegate = Delegate.create(this, onPingStartListener);
		pingProgressDelegate = Delegate.create(this, onPingProgressListener);
		pingErrorDelegate = Delegate.create(this, onPingErrorListener);
		preloadStartDelegate = Delegate.create(this, onPreloadStartListener);
		preloadProgressDelegate = Delegate.create(this, onPreloadProgressListener);
		preloadInitDelegate = Delegate.create(this, onPreloadInitListener);
		preloadErrorDelegate = Delegate.create(this, onPreloadErrorListener);
		
	}
	
	
	// Functions
	
	public function dispatchEvent(eventObj:Object):Void {}
	public function addEventListener(event:String, handler):Void {}
	public function removeEventListener(event:String, handler):Void {}
	
	public function ping():Void {
		
		log.debug("ping(), pages.length = " + pages.length);
		
		_isPinging = true;
		pingingPages = new Array();
		_pingedPages = new Array();
		
		var eventObject:Object = {target: this, type: EventConstants.PAGES_PING_START};
		dispatchEvent(eventObject);
		
		pingContainer_mc = _root.createEmptyMovieClip(PagesConf.pingContainerName, 
																	_root.getNextHighestDepth());
		pingContainer_mc._visible = false;
		
		for (var i:Number = 0; i < pages.length; i++) {
			
			var p:PageContext = pages[i];
			
			var pageLoad:PageLoad = doPageLoad(p, pingContainer_mc);
			var mclListener:Object = new Object();
			mclListener.onLoadStart = pingStartDelegate;
			mclListener.onLoadProgress = pingProgressDelegate;
			mclListener.onLoadError = pingErrorDelegate;
			pageLoad.mcl.addListener(mclListener);
		
			pingingPages.push(pageLoad);
			
		}
		
	}
	
	function stopPing():Void {
		
		log.debug("stopPing()");
		
		_isPinging = false;
		
		for (var i:Number = 0; i < pingingPages.length; i++) {
			var pageLoad:PageLoad = pingingPages[i];
			pageLoad.mcl.unloadClip(pageLoad.target_mc);
			delete pageLoad.mcl;
			pageLoad.target_mc.removeMovieClip();
			delete pageLoad.target_mc;
		}
		
		var eventObject:Object = {target: this, type: EventConstants.PAGES_PING_STOP};
		dispatchEvent(eventObject);
		
	}
	
	public function preload():Void {
		
		log.debug("preload(), pages.length = " + pages.length);
		
		_isPreloading = true;
		
		var eventObject:Object = {target: this, type: EventConstants.PAGES_PRELOAD_START};
		dispatchEvent(eventObject);
		
		preloadContainer_mc = _root.createEmptyMovieClip(PagesConf.preloadContainerName, _root.getNextHighestDepth());
		preloadContainer_mc._visible = false;
			
		preloadNext(Math.min(pages.length, PagesConf.nConcurrentPreload));
		
	}
	
	public function stopPreload():Void {
		
		log.debug("stopPreload()");
		
		isPreloadStop = true;
		
		for (var i:Number = 0; i < preloadingPages.length; i++) {
			var pageLoad:PageLoad = preloadingPages[i];
			pageLoad.mcl.unloadClip(pageLoad.target_mc);
			delete pageLoad.mcl;
			pageLoad.target_mc.removeMovieClip();
			delete pageLoad.target_mc;
		}
		
		preloadingPages = new Array();
		
		var eventObject:Object = {target: this, type: EventConstants.PAGES_PRELOAD_STOP};
		dispatchEvent(eventObject);
			
	}
	
	public function restartPreload():Void {
		
		log.debug("stopPreload()");
		
		if (!isPreloadStop)
			throw new PreloaderNotStoppedError();
		
		isPreloadStop = false;
		
		var n:Number = Math.min(pages.length - preloadedPages.length, PagesConf.nConcurrentPreload);		preloadNext(n);
		
		var eventObject:Object = {target: this, type: EventConstants.PAGES_PRELOAD_RESTART};
		dispatchEvent(eventObject);
			
	}
	
	private function discriminateExternal(pages:Array):Array {
		var tmpPages:Array = new Array();
		for (var i:Number = 0; i < pages.length; i++) {
			var p:PageContext = pages[i];
			if (p.type == PageBuilder.TYPE_EXTERNAL || p.type == null)
				tmpPages.push(p);
		}
		return tmpPages;
	}
	
	private function doPageLoad(page:PageContext, container_mc:MovieClip):PageLoad {
	
		var p_mc:MovieClip = container_mc.createEmptyMovieClip(page.name, container_mc.getNextHighestDepth());
		var mcl:MovieClipLoader = new MovieClipLoader();
		mcl.loadClip(page.target, p_mc);
		
		var pageLoad:PageLoad = new PageLoad();
		pageLoad.page = page;
		pageLoad.mcl = mcl;
		pageLoad.target_mc = p_mc;
		
		return pageLoad;
	
	}
	
	private function doPagePreload(page:PageContext, container_mc:MovieClip):Void {
	
		var pageLoad:PageLoad = doPageLoad(page, container_mc);
		var mclListener:Object = new Object();
		mclListener.onLoadStart = preloadStartDelegate;
		mclListener.onLoadProgress = preloadProgressDelegate;
		mclListener.onLoadInit = preloadInitDelegate;
		mclListener.onLoadError = preloadErrorDelegate;
		pageLoad.mcl.addListener(mclListener);
	
		preloadingPages.push(pageLoad);
	
	}
	
	private function preloadNext(limit:Number):Void {
		for (var i:Number = 0; i < limit; i++) {
			var iNext:Number = _preloadedPages.length + preloadingPages.length;
			if (iNext < pages.length)
				doPagePreload(pages[iNext], preloadContainer_mc);
		}
	}
	
	private function findPageLoad(target_mc:MovieClip, pageLoads:Array):PageLoad {
		var pageLoad:PageLoad;
		for (var i:Number = 0; i < pageLoads.length; i++) {
			if (pageLoads[i].target_mc == target_mc) {
				pageLoad = pageLoads[i];
				break;
			}
		}
		return pageLoad;
	}
	
	
	// Listerners
	
	private function onPingStartListener(target_mc:MovieClip):Void {
		
		var pageLoad:PageLoad = findPageLoad(target_mc, pingingPages);
		pageLoad.timeStart = new Date();
		
		log.debug("onPingStartListener(), " + pageLoad.page.name);
		
		var eventObject:Object = {target: pageLoad, type: EventConstants.PAGE_PING_START};
		dispatchEvent(eventObject);	
		
	}
	
	private function onPingProgressListener(target_mc:MovieClip, bytesLoaded:Number, bytesTotal:Number):Void {
		
		var pageLoad:PageLoad = findPageLoad(target_mc, pingingPages);
		pageLoad.bytesLoaded = bytesLoaded;
		pageLoad.bytesTotal = bytesTotal;
		pageLoad.totalframes = target_mc._totalframes;
		pageLoad.timeEnd = new Date();
		
		log.debug("onPingProgressListener(), " + pageLoad.page.name);		log.debug("time = " + pageLoad.time);
		
		_pingedPages.push(pageLoad);
		
		var eventObject:Object = {target: pageLoad, type: EventConstants.PAGE_PING_COMPLETE};
		dispatchEvent(eventObject);
		
		target_mc.removeMovieClip();
		
		var index:Number;
		for (var i:Number = 0; i < pingingPages.length; i++) {
			if (pingingPages[i] == pageLoad) {
				index = i;
				break;
			}
		}
		pingingPages.splice(index, 1);
		
		if (_pingedPages.length == pages.length) {
			
			var timeMin:Date;
			var timeMax:Date;
			for (var i:Number = 0; i < _pingedPages.length; i++) {
				if (timeMin == null || _pingedPages[i].timeStart < timeMin)
					timeMin = _pingedPages[i].timeStart;
				if (timeMax == null || _pingedPages[i].timeEnd > timeMax)
					timeMax = _pingedPages[i].timeEnd;
			}
			var time:Number = timeMax.getTime() - timeMin.getTime();
			
			pingContainer_mc.removeMovieClip();
			
			_isPinging = false;
			_isPinged = true;
			
			log.info("Pages ping completed in " + time);
			
			var eventObject:Object = {target: this, type: EventConstants.PAGES_PING_COMPLETE, pages: pingedPages, 
									  timeStart: timeMin, timeEnd: timeMax, time: time};
			dispatchEvent(eventObject);
			
		}
			
	}
	
	private function onPingErrorListener(target_mc:MovieClip, errorCode:String, httpStatus:Number):Void {
		
		var pageLoad:PageLoad = findPageLoad(target_mc, pingingPages);
		
		log.warning("onPingErrorListener(), time = " + pageLoad.time);
			
		var eventObject:Object = {target: pageLoad, type: EventConstants.PAGE_PING_ERROR, 
								  errorCode: errorCode, httpStatus: httpStatus};
		dispatchEvent(eventObject);
		
	}
	
	private function onPreloadStartListener(target_mc:MovieClip):Void {
		
		var pageLoad:PageLoad = findPageLoad(target_mc, preloadingPages);
		pageLoad.timeStart = new Date();
		
		log.debug("onPreloadStartListener(), " + pageLoad.page.name);
	
		var eventObject:Object = {target: pageLoad, type: EventConstants.PAGE_PRELOAD_START};
		dispatchEvent(eventObject);
		
	}
	
	private function onPreloadProgressListener(target_mc:MovieClip, bytesLoaded:Number, bytesTotal:Number):Void {
		
		var pageLoad:PageLoad = findPageLoad(target_mc, preloadingPages);
		pageLoad.bytesLoaded = bytesLoaded;
		pageLoad.bytesTotal = bytesTotal;
		pageLoad.totalframes = target_mc._totalframes;
		
		log.debug("onPreloadProgressListener(), " + pageLoad.page.name);		log.debug("bytesLoaded = " + bytesLoaded);		log.debug("bytesTotal = " + bytesTotal);
		
		var eventObject:Object = {target: pageLoad, type: EventConstants.PAGE_PRELOAD_PROGRESS};
		dispatchEvent(eventObject);
			
	}
	
	private function onPreloadInitListener(target_mc:MovieClip):Void {
		
		var pageLoad:PageLoad = findPageLoad(target_mc, preloadingPages);
		pageLoad.totalframes = target_mc._totalframes;
		pageLoad.timeEnd = new Date();
		
		log.debug("onPreloadInitListener(), " + pageLoad.page.name);
		log.debug("time = " + pageLoad.time);
		
		_preloadedPages.push(pageLoad);
		
		var eventObject:Object = {target: pageLoad, type: EventConstants.PAGE_PRELOAD_INIT};
		dispatchEvent(eventObject);
		
		target_mc.removeMovieClip();
		
		var index:Number;
		for (var i:Number = 0; i < preloadingPages.length; i++) {
			if (preloadingPages[i] == pageLoad) {
				index = i;
				break;
			}
		}
		preloadingPages.splice(index, 1);
		
		preloadNext(1);
		
		if (_preloadedPages.length == pages.length) {
			
			var timeMin:Date;
			var timeMax:Date;
			for (var i:Number = 0; i < _preloadedPages.length; i++) {
				if (timeMin == null || _preloadedPages[i].timeStart < timeMin)
					timeMin = _preloadedPages[i].timeStart;
				if (timeMax == null || _preloadedPages[i].timeEnd > timeMax)
					timeMax = _preloadedPages[i].timeEnd;
			}
			var time:Number = timeMax.getTime() - timeMin.getTime();
			
			preloadContainer_mc.removeMovieClip();
			
			_isPreloading = false;
			_isPreloaded = true;
			
			log.info("Pages preload complete in " + time);
			
			var eventObject:Object = {target: this, type: EventConstants.PAGES_PRELOAD_INIT, pages: preloadedPages, 
									  timeStart: timeMin, timeEnd: timeMax, time: time};
			dispatchEvent(eventObject);
			
		}
			
	}
	
	private function onPreloadErrorListener(target_mc:MovieClip, errorCode:String, httpStatus:Number):Void {
		
		var pageLoad:PageLoad = findPageLoad(target_mc, preloadingPages);
		
		log.warning("onPreloadErrorListener(), time = " + pageLoad.time);
		
		var eventObject:Object = {target: pageLoad, type: EventConstants.PAGE_PRELOAD_ERROR, 
								  errorCode: errorCode, httpStatus: httpStatus};
		dispatchEvent(eventObject);
		
	}
	
	
	// Getter/Setter
	
	public function get pages():Array {
		return _pages;
	}
	
	public function set pages(pages:Array):Void {
		_pages = discriminateExternal(pages);
	}
	
	public function get pingedPages():Array {
		return _pingedPages;
	}
	
	public function get preloadedPages():Array {
		return _preloadedPages;
	}
	
	public function get isPinging():Boolean {
		return _isPinging;
	}
	
	public function get isPreloading():Boolean {
		return _isPreloading;
	}
	
	public function get isPinged():Boolean {
		return _isPinged;
	}
	
	public function get isPreloaded():Boolean {
		return _isPreloaded;
	}


}